package top.lgd.service.person;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.lgd.base.LgdConstants;
import top.lgd.base.LgdException;
import top.lgd.enums.AccontFlowTypeEnum;
import top.lgd.enums.AccountAttrEnum;
import top.lgd.enums.AccountOpTypeEnum;
import top.lgd.enums.AccountTypeEnum;
import top.lgd.mapper.person.AccountMapper;
import top.lgd.pojo.entity.person.Account;
import top.lgd.pojo.entity.person.AccountFlow;
import top.lgd.pojo.req.person.AccountOpReq;
import top.lgd.service.LgdCacheService;
import top.lgd.utils.DateUtils;
import top.lgd.utils.IdUtils;
import top.lgd.utils.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;

/**
 * @author YYJ
 * @version 1.0
 */
@Service
@Slf4j
public class AccountService extends ServiceImpl<AccountMapper, Account> implements IService<Account> {

    @Autowired
    private AccountFlowService accountFlowService;

    @Autowired
    private LgdCacheService lgdCacheService;

    /**
     * 【有缓存】
     *
     * @param userId
     * @return
     */
    public List<Account> queryAccountList(Long userId, String accountType) {
        List<Account> accountList = new ArrayList<>();
        if (null != userId && 0 != userId) {
            String key = LgdConstants.KEY_PERSON_ACCOUNT_LIST + userId + "-" + accountType;
            accountList = lgdCacheService.get(key);
            if (null != accountList) {
                return accountList;
            }

            accountList = list(new QueryWrapper<Account>().eq(LgdConstants.COLUMN_DATA_USER_ID, userId)
                    .eq(LgdConstants.COLUMN_ACCOUNT_TYPE, accountType)
                    .gt(LgdConstants.COLUMN_STATUS, 0)
                    .orderByAsc(LgdConstants.COLUMN_SORT_NUM));
            if (null != accountList) {
                lgdCacheService.put(key, accountList);
            }
        }
        return accountList;
    }

    /**
     * 【有缓存】
     *
     * @param accountId
     * @return
     */
    public Account queryAccount(Long accountId) {
        Account account = null;
        if (null != accountId && 0 != accountId) {
            String key = LgdConstants.KEY_PERSON_ACCOUNT_INFO + accountId;
            account = lgdCacheService.get(key);
            if (null != account) {
                return account;
            }

            account = getById(accountId);
            if (null != account) {
                lgdCacheService.put(key, account);
            }
        }
        return account;
    }

    /**
     * 重算 信用卡还款日-排序
     */
    public void recountSortNum(AccountTypeEnum accountTypeEnum) {
        List<Account> list = this.list(new QueryWrapper<Account>().eq(LgdConstants.COLUMN_ACCOUNT_TYPE, accountTypeEnum.getType()));
        list.forEach(account -> {
            final LocalDate dueDate = DateUtils.getBillDate(account.getDueDay());
            account.setSortNum((int) DateUtils.between(LocalDate.now(), dueDate, ChronoUnit.DAYS));
            if (1 == account.getSortNum()) {
                account.setBillFlag(1);
            }
        });
        updateBatchById(list);
    }

    /**
     * 新增 账户和流水
     *
     * @param account
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean addAccountAndFlow(Account account) {
        AccountTypeEnum accountTypeEnum = AccountTypeEnum.valueOf(account.getAccountType());
        String bizDesc;
        Long tradeAccountId = LgdConstants.PERSON_ACCOUNT_SYSTEM_ID;
        AccountOpTypeEnum accountOpTypeEnum = AccountOpTypeEnum.transIn;

        switch (accountTypeEnum) {
            case finance:
                account.setInAmount(null == account.getInAmount() ? BigDecimal.ZERO : account.getInAmount());
                account.setOutAmount(null == account.getOutAmount() ? BigDecimal.ZERO : account.getOutAmount());
                bizDesc = "金融账户初始";
                break;
            case claim:
                account.setOutAmount(null == account.getOutAmount() ? BigDecimal.ZERO : account.getOutAmount());
                bizDesc = "债权账户初始【" + account.getRemark() + "】";
                tradeAccountId = LgdConstants.PERSON_ACCOUNT_SWAP_ID;
                accountOpTypeEnum = AccountOpTypeEnum.lendTo;
                break;
            case credit:
                bizDesc = "信用账户初始";
                tradeAccountId = LgdConstants.PERSON_ACCOUNT_SWAP_ID;
                break;
            case loan:
                bizDesc = "贷款账户初始";
                tradeAccountId = LgdConstants.PERSON_ACCOUNT_SWAP_ID;
                break;
            case debt:
                account.setOutAmount(null == account.getOutAmount() ? BigDecimal.ZERO : account.getOutAmount());
                bizDesc = "债务账户初始【" + account.getRemark() + "】";
                tradeAccountId = LgdConstants.PERSON_ACCOUNT_SWAP_ID;
                break;
            default:
                throw new LgdException("不支持的账户类型");
        }

        account.setBalance(account.getInAmount().subtract(account.getOutAmount()));
        if (save(account)) {
            return saveAccountFlow(account, LocalDate.now(), accountOpTypeEnum,
                    BigDecimal.ZERO, account.getBalance(), BigDecimal.ZERO,
                    accountTypeEnum, bizDesc, tradeAccountId);
        }
        return false;
    }

    /**
     * 更新 账户和流水
     *
     * @param account
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean updateAccountAndFlow(Account account, AccountOpReq accountOpReq) {
        AccountOpTypeEnum opTypeEnum = AccountOpTypeEnum.valueOf(accountOpReq.getOpType());
        BigDecimal opAmount = accountOpReq.getOpAmount();
        LocalDate flowDate = null != accountOpReq.getOpDate() ? accountOpReq.getOpDate().plusDays(1) : LocalDate.now();
        String bizDesc = accountOpReq.getRemark();
        final BigDecimal beforeAmount = account.getBalance();

        if (AccountOpTypeEnum.check == opTypeEnum) {
            opAmount = account.getOutAmount().subtract(opAmount);

            if (accountOpReq.getBillAmount().compareTo(BigDecimal.ZERO) > 0) {
                account.setBillAmount(accountOpReq.getBillAmount());
            }

            if (opAmount.compareTo(BigDecimal.ZERO) == 0) {
                log.info("仅设置账单金额");
                // 清除缓存
                clearCache(account.getId(), account.getDataUserId(), account.getAccountType());
                return updateById(account);
            } else if (opAmount.compareTo(BigDecimal.ZERO) > 0) {
                opTypeEnum = AccountOpTypeEnum.consume;
                bizDesc = "信用卡对账->消费";
            } else {
                opAmount = opAmount.negate();
                opTypeEnum = AccountOpTypeEnum.refund;
                bizDesc = "信用卡对账->还款";
            }
        }

        Long tradeAccountId = LgdConstants.PERSON_ACCOUNT_SWAP_ID;
        if (AccountOpTypeEnum.transIn == opTypeEnum) {
            // 金融转入
            account.setInAmount(account.getInAmount().add(opAmount));
            tradeAccountId = LgdConstants.PERSON_ACCOUNT_SYSTEM_ID;
        } else if (AccountOpTypeEnum.transOut == opTypeEnum) {
            // 金融转出
            account.setOutAmount(account.getOutAmount().add(opAmount));
            tradeAccountId = LgdConstants.PERSON_ACCOUNT_SYSTEM_ID;
        } else if (AccountOpTypeEnum.lendBack == opTypeEnum) {
            // 债权偿还
            account.setOutAmount(account.getOutAmount().add(opAmount));
        } else if (AccountOpTypeEnum.consume == opTypeEnum) {
            // 信用卡消费
            account.setOutAmount(account.getOutAmount().subtract(opAmount));
        } else if (AccountOpTypeEnum.refund == opTypeEnum) {
            // 信用卡还款
            BigDecimal billAmount = account.getBillAmount().subtract(opAmount);
            if (billAmount.compareTo(BigDecimal.ZERO) > 0) {
                account.setBillAmount(billAmount);
            } else {
                account.setBillAmount(BigDecimal.ZERO);
                account.setBillFlag(2);
            }
            account.setOutAmount(account.getOutAmount().add(opAmount));
            opAmount = opAmount.negate();
        } else if (AccountOpTypeEnum.loanBack == opTypeEnum) {
            // 贷款还款
            account.setOutAmount(account.getOutAmount().add(opAmount.subtract(accountOpReq.getInterests())));
            opAmount = opAmount.subtract(accountOpReq.getInterests()).negate();
            account.setBillFlag(2);
        } else if (AccountOpTypeEnum.borrowBack == opTypeEnum) {
            // 债务还款
            account.setOutAmount(account.getOutAmount().add(opAmount));
        } else if (AccountOpTypeEnum.salary == opTypeEnum) {
            account.setInAmount(account.getInAmount().add(opAmount));
            tradeAccountId = LgdConstants.PERSON_ACCOUNT_SYSTEM_ID;
        } else {
            throw new LgdException("不支持的操作类型");
        }

        account.setBalance(account.getInAmount().subtract(account.getOutAmount()));
        if (updateById(account)) {
            // 清除缓存
            clearCache(account.getId(), account.getDataUserId(), account.getAccountType());
            return saveAccountFlow(account, flowDate, opTypeEnum, beforeAmount,
                    opAmount, accountOpReq.getInterests(), AccountTypeEnum.valueOf(account.getAccountType()),
                    bizDesc, tradeAccountId);
        }
        return false;
    }

    /**
     * 保存 账户流水信息
     *
     * @param account
     * @param opTypeEnum
     * @param opAmount
     * @param accountTypeEnum
     * @param bizDesc
     * @param tradeAccountId
     * @return
     */
    private boolean saveAccountFlow(Account account, LocalDate opDate, AccountOpTypeEnum opTypeEnum,
                                    BigDecimal beforeAmount, BigDecimal opAmount, BigDecimal interests,
                                    AccountTypeEnum accountTypeEnum, String bizDesc, Long tradeAccountId) {
        AccountFlow accountFlow = new AccountFlow();
        accountFlow.setAccountId(account.getId());
        accountFlow.setAccountAttr(account.getAccountAttr());
        accountFlow.setDataUserId(account.getDataUserId());
        accountFlow.setBizNo(IdUtils.getSnowflakeNextIdStr());
        accountFlow.setFlowDate(opDate);
        accountFlow.setBeforeAmount(beforeAmount);
        accountFlow.setFlowAmount(opAmount.abs());
        accountFlow.setAfterAmount(account.getBalance());
        accountFlow.setTradeAccountId(tradeAccountId);
        final Account tradeAccount = queryAccount(tradeAccountId);
        final BigDecimal tradeBeforeAmount = tradeAccount.getBalance();

        // 优先使用些逻辑判断
        switch (opTypeEnum) {
            case lendTo:
                // 债权借出
                accountFlow.setFlowType(AccontFlowTypeEnum.IN.name());
                accountFlow.setRemark(opTypeEnum.getDesc() + "：" + accountFlow.getFlowAmount() + accountTypeEnum.getBalanceDesc() + account.getBalance());
                break;
            case lendBack:
                // 债权偿还
                accountFlow.setFlowType(AccontFlowTypeEnum.OUT.name());
                accountFlow.setRemark(opTypeEnum.getDesc() + "：" + accountFlow.getFlowAmount() + accountTypeEnum.getBalanceDesc() + account.getBalance());
                break;
            case consume:
                // 信用卡消费
                accountFlow.setFlowType(AccontFlowTypeEnum.OUT.name());
                accountFlow.setRemark(opTypeEnum.getDesc() + "：" + accountFlow.getFlowAmount() + accountTypeEnum.getBalanceDesc() + account.getBalance());
                break;
            case refund:
                // 信用卡还款
                accountFlow.setFlowType(AccontFlowTypeEnum.IN.name());
                accountFlow.setRemark(opTypeEnum.getDesc() + "：" + accountFlow.getFlowAmount() + accountTypeEnum.getBalanceDesc() + account.getBalance());
                break;
            case loanIn:
                //贷款贷入
                accountFlow.setFlowType(AccontFlowTypeEnum.IN.name());
                accountFlow.setRemark(opTypeEnum.getDesc() + "：" + accountFlow.getFlowAmount() + accountTypeEnum.getBalanceDesc() + account.getBalance());
                break;
            case loanBack:
                //贷款还款
                accountFlow.setFlowType(AccontFlowTypeEnum.OUT.name());
                accountFlow.setRemark(opTypeEnum.getDesc() + "：" + accountFlow.getFlowAmount() + accountTypeEnum.getBalanceDesc() + account.getBalance());
                if (null != tradeAccount) {
                    // 有利息支出
                    if (interests.compareTo(BigDecimal.ZERO) > 0) {
                        final Account inTradeAccount = queryAccount(LgdConstants.PERSON_ACCOUNT_SYSTEM_ID);
                        final BigDecimal inTradeBeforeAmount = inTradeAccount.getBalance();
                        dealTradeAccountAndFlow(inTradeAccount, AccontFlowTypeEnum.IN.name(),
                                opDate, inTradeBeforeAmount,
                                interests, accountFlow.getBizNo(), bizDesc, account);
                        //缓存利息
                        lgdCacheService.put(accountFlow.getBizNo(), interests);
                    }
                }
                break;
            case borrowIn:
                // 债务借入
                accountFlow.setFlowType(AccontFlowTypeEnum.IN.name());
                accountFlow.setRemark(opTypeEnum.getDesc() + "：" + accountFlow.getFlowAmount() + accountTypeEnum.getBalanceDesc() + account.getBalance());
                break;
            case borrowBack:
                // 债务还款
                accountFlow.setFlowType(AccontFlowTypeEnum.OUT.name());
                accountFlow.setRemark(opTypeEnum.getDesc() + "：" + accountFlow.getFlowAmount() + accountTypeEnum.getBalanceDesc() + account.getBalance());
                break;
        }

        // 逐渐迁移逻辑判断
        switch (accountTypeEnum) {
            case finance:
                accountFlow.setFlowType(opAmount.compareTo(BigDecimal.ZERO) >= 0 ? AccontFlowTypeEnum.IN.name() : AccontFlowTypeEnum.OUT.name());
                accountFlow.setRemark((opAmount.compareTo(BigDecimal.ZERO) >= 0 ?
                        AccountOpTypeEnum.transIn.getDesc() + "：" + accountFlow.getFlowAmount() :
                        AccountOpTypeEnum.transOut.getDesc() + "：" + accountFlow.getFlowAmount())
                        + accountTypeEnum.getBalanceDesc() + account.getBalance());
                break;
        }

        if (null != tradeAccount) {
            // 处理交易对方账户及流水
            dealTradeAccountAndFlow(tradeAccount, accountFlow.getFlowType(), opDate, tradeBeforeAmount,
                    accountFlow.getFlowAmount(), accountFlow.getBizNo(), bizDesc, account);
        }
        accountFlow.setBizDesc(StringUtils.isNotEmpty(bizDesc) ? bizDesc : accountFlow.getRemark());
        return accountFlowService.save(accountFlow);
    }

    /**
     * 处理 交易方账号和流水
     *
     * @param tradeAccount
     * @param flowType
     * @param flowAmount
     * @param account
     * @return
     */
    private boolean dealTradeAccountAndFlow(Account tradeAccount, String flowType, LocalDate opDate,
                                            BigDecimal beforeAmount, BigDecimal flowAmount,
                                            String bizNo, String bizDesc, Account account) {
        AccountTypeEnum tradeAccountTypeEnum = AccountTypeEnum.valueOf(tradeAccount.getAccountType());
        AccountFlow tradeAccountFlow = new AccountFlow();
        tradeAccountFlow.setAccountId(tradeAccount.getId());
        tradeAccountFlow.setAccountAttr(tradeAccount.getAccountAttr());
        tradeAccountFlow.setFlowDate(opDate);
        tradeAccountFlow.setBeforeAmount(beforeAmount);
        //缓存利息
        BigDecimal interests = lgdCacheService.get(bizNo);
        if (null != interests) {
            tradeAccountFlow.setFlowAmount(flowAmount.add(interests));
        } else {
            tradeAccountFlow.setFlowAmount(flowAmount);
        }

        tradeAccountFlow.setTradeAccountId(account.getId());
        tradeAccountFlow.setDataUserId(account.getDataUserId());
        tradeAccountFlow.setBizNo(bizNo);

        if (AccountAttrEnum.A.name().equals(account.getAccountAttr())) {
            // 资产类：当前账户【借】->交易账户【贷】
            if (AccontFlowTypeEnum.IN.name().equals(flowType)) {
                tradeAccount.setOutAmount(tradeAccount.getOutAmount().add(flowAmount));
                tradeAccount.setBalance(tradeAccount.getInAmount().subtract(tradeAccount.getOutAmount()));
                tradeAccountFlow.setFlowType(AccontFlowTypeEnum.OUT.name());
                if (null != interests) {
                    tradeAccountFlow.setRemark(AccountOpTypeEnum.transOut.getDesc() + "：" + flowAmount + " = "
                            + AccountOpTypeEnum.interest.getDesc() + "：" + interests + " + "
                            + "本金：" + flowAmount.subtract(interests)
                            + tradeAccountTypeEnum.getBalanceDesc() + tradeAccount.getBalance());
                } else {
                    tradeAccountFlow.setRemark(AccountOpTypeEnum.transOut.getDesc() + "：" + flowAmount
                            + tradeAccountTypeEnum.getBalanceDesc() + tradeAccount.getBalance());
                }
            } else {
                tradeAccount.setInAmount(tradeAccount.getInAmount().add(flowAmount));
                tradeAccount.setBalance(tradeAccount.getInAmount().subtract(tradeAccount.getOutAmount()));
                tradeAccountFlow.setFlowType(AccontFlowTypeEnum.IN.name());
                tradeAccountFlow.setRemark(AccountOpTypeEnum.transIn.getDesc() + "：" + flowAmount
                        + tradeAccountTypeEnum.getBalanceDesc() + tradeAccount.getBalance());
            }
        } else {
            // 负债类：当前账户【借】->交易账户【借】
            if (AccontFlowTypeEnum.IN.name().equals(flowType)) {
                tradeAccount.setInAmount(tradeAccount.getInAmount().add(flowAmount));
                tradeAccount.setBalance(tradeAccount.getInAmount().subtract(tradeAccount.getOutAmount()));
                tradeAccountFlow.setFlowType(AccontFlowTypeEnum.IN.name());
                tradeAccountFlow.setRemark(AccountOpTypeEnum.transIn.getDesc() + "：" + flowAmount
                        + tradeAccountTypeEnum.getBalanceDesc() + tradeAccount.getBalance());
            } else {
                if (null != interests) {
                    tradeAccount.setOutAmount(tradeAccount.getOutAmount().add(flowAmount).add(interests));
                    tradeAccountFlow.setRemark(AccountOpTypeEnum.transOut.getDesc() + "：" + flowAmount.add(interests)
                            + tradeAccountTypeEnum.getBalanceDesc() + tradeAccount.getBalance());
                } else {
                    tradeAccount.setOutAmount(tradeAccount.getOutAmount().add(flowAmount));
                    tradeAccountFlow.setRemark(AccountOpTypeEnum.transOut.getDesc() + "：" + flowAmount
                            + tradeAccountTypeEnum.getBalanceDesc() + tradeAccount.getBalance());
                }
                tradeAccount.setBalance(tradeAccount.getInAmount().subtract(tradeAccount.getOutAmount()));
                tradeAccountFlow.setFlowType(AccontFlowTypeEnum.OUT.name());
            }
        }

        tradeAccountFlow.setBizDesc(StringUtils.isNotEmpty(bizDesc) ? bizDesc : tradeAccountFlow.getRemark());
        tradeAccountFlow.setAfterAmount(tradeAccount.getBalance());
        accountFlowService.save(tradeAccountFlow);
        // 清除缓存
        clearCache(tradeAccount.getId(), tradeAccount.getDataUserId(), tradeAccount.getAccountType());
        return updateById(tradeAccount);
    }

    private void clearCache(Long accountId, Long userId, String accountType) {
        lgdCacheService.delete(LgdConstants.KEY_PERSON_ACCOUNT_INFO + accountId);
        lgdCacheService.delete(LgdConstants.KEY_PERSON_ACCOUNT_LIST + userId + "-" + accountType);
    }


}